/*
OpenMVL Matrix Vector Library
Copyright (c) 2009 FluidInteractive (R)
 
This software is provided 'as-is', without any express or implied warranty.
In no event will the authors be held liable for any damages arising
from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it freely,
subject to the following restrictions:
 
1. The origin of this software must not be misrepresented; you must
not claim that you wrote the original software. If you use this
software in a product, an acknowledgment in the product documentation
would be appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must
not be misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
 
Written by: Nicola Candussi <nicola@fluidinteractive.com>
*/

//layout_prod.hpp

#ifndef MVL_LAYOUT_PROD_HPP
#define MVL_LAYOUT_PROD_HPP

#include "layout.hpp"
#include "layout_trans.hpp"

namespace mvl {

namespace layout {

//dot products of layouts: L1 is row major and L2 is column major
// result is row major

//compute the dot product of 2 sparse vectors of entries ie. the result is in the form of a tag: _0_, _v_ or _1_
template<typename LIt1, typename LIt2>
struct layout_dot {
    typedef typename begin2<LIt1>::type begin1;
    typedef typename begin2<LIt2>::type begin2;
    typedef typename end2<LIt1>::type end1;
    typedef typename end2<LIt2>::type end2;

    template<typename It1, typename It2, typename Result, typename Condition>
    struct state {
        typedef It1 it1;
        typedef It2 it2;
        typedef Result result;
        typedef Condition condition;
        typedef state type;
    };

    template<typename PrevTag, typename Tag1, typename Tag2> struct compute_tag : 
        add_tags<
            PrevTag,
            typename mul_tags<
                Tag1,
                Tag2
            >::type
        > { };

    struct update {
        template<typename State>
        struct apply {
            typedef typename meta::deref<typename State::it1>::type e1;
            typedef typename meta::deref<typename State::it2>::type e2;
            typedef typename e1::j j1;
            typedef typename e2::i i2;
            typedef typename meta::eval_if<
                typename meta::less_equal<j1, i2>::type,
                meta::next<typename State::it1>,
                meta::identity<typename State::it1>
            >::type new_it1;

            typedef typename meta::eval_if<
                typename meta::less_equal<i2, j1>::type,
                meta::next<typename State::it2>,
                meta::identity<typename State::it2>
            >::type new_it2;

            typedef typename meta::eval_if<
                typename meta::equal_to<j1, i2>::type,
                compute_tag<
                    typename State::result, 
                    typename e1::tag,
                    typename e2::tag
                >,
                meta::identity<typename State::result>
            >::type new_result;
                                    
            typedef typename meta::and_<
                typename meta::not_equal_to<new_it1, end1>::type,
                typename meta::not_equal_to<new_it2, end2>::type
            >::type new_condition;

            typedef state<new_it1, new_it2, new_result, new_condition> type;
        };
    };

    typedef typename meta::while_<
        state<
            begin1,
            begin2,
            _0_,
            typename meta::and_<
                typename meta::not_equal_to<begin1, end1>::type,
                typename meta::not_equal_to<begin2, end2>::type
            >::type
        >,
        update
    >::type::result type;
};

//sparse_prod
template<typename L1, typename L2>
struct layout_prod {
    typedef row_major_tag order;
    typedef L1 layout1_t;
    typedef L2 layout2_t;

    typedef typename layout::begin1<layout1_t>::type begin1;
    typedef typename layout::begin1<layout2_t>::type begin2;
    typedef typename layout::end1<layout1_t>::type end1;
    typedef typename layout::end1<layout2_t>::type end2;

    template<typename Data, typename It, typename NumVals>
    struct state1 {
        typedef Data        data;
        typedef It          it;
        typedef NumVals     num_vals;
        typedef state1      type;
    };

    struct back_inserter1 {
        template<typename State, typename It>
        struct apply {
            typedef typename layout_dot<
                typename State::it,
                It
            >::type tag;
            typedef typename meta::distance<
                begin1,
                typename State::it
            >::type i;
            typedef typename meta::distance<
                begin2,
                It
            >::type j;

            typedef typename meta::eval_if<
                typename meta::is_same<tag, _0_>::type,
                meta::identity<typename State::data>,
                meta::push_back<
                    typename State::data,
                    entry<i, j, tag>
                >
            >::type new_data;
            typedef typename meta::eval_if<
                typename meta::is_same<tag, _v_>::type,
                typename meta::next<typename State::num_vals>::type,
                meta::identity<typename State::num_vals>
            >::type new_num_vals;
            typedef state1<new_data, typename State::it, new_num_vals> type;
        };

    };

    template<typename Data, typename NumVals>
    struct state2 {
        typedef Data    data;
        typedef NumVals num_vals;
        typedef state2  type;
    };

    struct back_inserter2 {
        template<typename State, typename It> 
        struct apply {
            typedef typename meta::iter_fold<
                begin2,
                end2,
                state1<
                    meta::vector<>,
                    It,
                    typename State::num_vals
                >,
                back_inserter1
            >::type result;
            typedef state2<
                typename meta::push_back<
                    typename State::data,
                    typename result::data
                >::type,
                typename result::num_vals    
            > type;
        };
    };

    typedef typename meta::iter_fold<
        begin1,
        end1,
        state2<
            meta::vector<>,
            meta::int_<0>
        >,
        back_inserter2
    >::type result;

    typedef typename meta::eval_if<
        typename meta::equal_to<
            typename result::num_vals, meta::int_<L1::Rows::value * L2::Cols::value>
        >::type,
        dense_layout<L1::Rows::value, L2::Cols::value, order>,
        sparse_layout<L1::Rows::value, L2::Cols::value, order, typename result::data, result::num_vals::value>
    >::type type;
};

} //namespace layout

} //namespace mvl

#endif
